/*
 * Copyright (c) 2023 Hisilicon Technologies Co., Ltd.
 * Hikptool is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *
 * See the Mulan PSL v2 for more details.
 */

#include "hikp_roce_ext_common.h"
#include <stddef.h>

static void hikp_roce_ext_reg_data_free(struct reg_data *reg)
{
	if (reg->offset) {
		free(reg->offset);
		reg->offset = NULL;
	}

	if (reg->data) {
		free(reg->data);
		reg->data = NULL;
	}
}

static const struct cmd_type_info {
	enum roce_cmd_type cmd_type;
	const char *cmd_name;
	uint8_t reg_array_length;
} cmd_info_table[] = {
	{GET_ROCEE_MDB_CMD, "MDB", ROCE_HIKP_MDB_REG_NUM_EXT},
	{GET_ROCEE_GMV_CMD, "GMV", ROCE_HIKP_GMV_REG_NUM_EXT},
	{GET_ROCEE_CAEP_CMD, "CAEP", ROCE_HIKP_CAEP_REG_NUM_EXT},
	{GET_ROCEE_PKT_CMD, "PKT", ROCE_HIKP_PKT_REG_NUM_EXT},
	{GET_ROCEE_SCC_CMD, "SCC", ROCE_HIKP_SCC_REG_NUM_EXT},
	{GET_ROCEE_QMM_CMD, "QMM", ROCE_HIKP_QMM_REG_NUM_EXT},
	{GET_ROCEE_TIMER_CMD, "TIMER", ROCE_HIKP_TIMER_REG_NUM_EXT},
	{GET_ROCEE_TRP_CMD, "TRP", ROCE_HIKP_TRP_REG_NUM_EXT},
	{GET_ROCEE_TSP_CMD, "TSP", ROCE_HIKP_TSP_REG_NUM_EXT},
	{GET_ROCEE_RST_CMD, "RST", ROCE_HIKP_RST_REG_NUM},
	{GET_ROCEE_GLOBAL_CFG_CMD, "GLOBAL_CFG", ROCE_HIKP_GLOBAL_CFG_REG_NUM},
	{GET_ROCEE_BOND_CMD, "BOND", ROCE_HIKP_BOND_REG_NUM},
	{GET_ROCEE_DFX_STA_CMD, "DFX_STA", ROCE_HIKP_DFX_STA_NUM_EXT},
};

static int get_cmd_info_table_idx(enum roce_cmd_type cmd_type)
{
	int array_size = sizeof(cmd_info_table) / sizeof(struct cmd_type_info);
	int i;

	for (i = 0; i < array_size; i++)
		if (cmd_type == cmd_info_table[i].cmd_type)
			return i;

	return -ENOENT;
}

static const char *get_cmd_name(enum roce_cmd_type cmd_type)
{
	int idx;

	idx = get_cmd_info_table_idx(cmd_type);
	if (idx >= 0)
		return cmd_info_table[idx].cmd_name;

	printf("Failed to get cmd name, cmd_type = %d\n", cmd_type);
	return NULL;
}

static int get_cmd_reg_array_length(enum roce_cmd_type cmd_type)
{
	int idx;

	idx = get_cmd_info_table_idx(cmd_type);
	if (idx >= 0)
		return cmd_info_table[idx].reg_array_length;

	printf("Failed to get cmd reg array length, cmd_type = %d\n", cmd_type);
	return idx;
}

static int fill_output(struct hikp_cmd_ret *cmd_ret,
			const char *cmd_name,
			uint32_t block_id,
			struct roce_ext_res_output *output)
{
	struct roce_ext_res_param *roce_ext_res = (struct roce_ext_res_param *)cmd_ret->rsp_data;
	struct roce_ext_head *res_head = &output->res_head;
	struct reg_data *reg = &output->reg;

	res_head->cur_block_num = roce_ext_res->head.cur_block_num;

	if (block_id != 0)
		return 0;

	res_head->total_block_num = roce_ext_res->head.total_block_num;
	res_head->flags = roce_ext_res->head.flags;
	if (!res_head->total_block_num) {
		printf("hikptool roce_%s total_block_num error!\n", cmd_name);
		return -EINVAL;
	}

	size_t per_val_size = res_head->flags & ROCE_HIKP_DATA_U64_FLAG ?
	sizeof(uint64_t) : sizeof(uint32_t);
	reg->offset = (uint32_t *)calloc(res_head->total_block_num, sizeof(uint32_t));
	output->per_val_size = per_val_size;
	reg->data = calloc(res_head->total_block_num, per_val_size);
	if (!reg->offset || !reg->data) {
		printf("hikptool roce_%s alloc log memory failed!\n", cmd_name);
		hikp_roce_ext_reg_data_free(reg);
		return -ENOMEM;
	}

	return 0;
}

static int validate_block_numbers(const struct roce_ext_head *res_head,
				  const char *cmd_name,
				  uint32_t block_id)
{
	uint32_t remain_block = res_head->total_block_num - block_id;

	if (!res_head->cur_block_num || res_head->cur_block_num > remain_block) {
		printf("hikptool roce_%s block size error, cur: %u, total: %u, remain: %u.\n",
			cmd_name, res_head->cur_block_num,
			res_head->total_block_num, remain_block);
		return -EINVAL;
	}

	return 0;
}

static int copy_data_from_response(struct hikp_cmd_ret *cmd_ret,
				   struct roce_ext_res_output *output,
				   int reg_array_length,
				   const char *cmd_name,
				   uint32_t block_id)
{
	struct roce_ext_res_param *roce_ext_res = (struct roce_ext_res_param *)cmd_ret->rsp_data;
	struct roce_ext_head *res_head = &output->res_head;
	struct reg_data *reg = &output->reg;
	size_t reg_data_offset;
	size_t offset_size;
	size_t data_size;
	void *dst_data;

	/*
	 * The data structure `roce_ext_res_param_u64` returned by the
	 * firmware is 8-byte aligned, so the offset of the `reg_data`
	 * member needs to be adjusted accordingly.
	 */
	if (res_head->flags & ROCE_HIKP_DATA_U64_FLAG)
		reg_data_offset = offsetof(struct roce_ext_res_param_u64, reg_data);
	else
		reg_data_offset = offsetof(struct roce_ext_res_param, reg_data);

	offset_size = res_head->cur_block_num * sizeof(uint32_t);
	data_size = res_head->cur_block_num * output->per_val_size;
	dst_data = reg->data_u32 + block_id * output->per_val_size / sizeof(uint32_t);
	if ((reg_data_offset + data_size) / sizeof(uint32_t) + (size_t)reg_array_length > cmd_ret->rsp_data_num) {
		printf("hikptool roce_%s cur size error, data_size: %zu, rsp_data_num: %u.\n",
			cmd_name, data_size, cmd_ret->rsp_data_num);
		return -EINVAL;
	}

	memcpy(reg->offset + block_id,
		(uint32_t *)&roce_ext_res->head + reg_data_offset / sizeof(uint32_t),
		offset_size);

	memcpy(dst_data, (uint32_t *)&roce_ext_res->head + reg_data_offset / sizeof(uint32_t) + reg_array_length,
		data_size);

	return 0;
}

static int hikp_roce_ext_get_res(enum roce_cmd_type cmd_type,
				 uint32_t block_id,
				 struct roce_ext_res_output *output,
				 int (*get_data)(struct hikp_cmd_ret **cmd_ret,
						 uint32_t block_id,
						 struct roce_ext_reg_name *reg_name))
{
	int reg_array_length = get_cmd_reg_array_length(cmd_type);
	struct roce_ext_reg_name *reg_name = &output->reg_name;
	struct roce_ext_head *res_head = &output->res_head;
	const char *cmd_name = get_cmd_name(cmd_type);
	struct reg_data *reg = &output->reg;
	struct hikp_cmd_ret *cmd_ret;
	int ret;

	/* reg_array_length greater than or equal to 0 ensures that cmd_name
	 * is not NULL, so cmd_name does not need to be checked.
	 */
	if (reg_array_length < 0)
		return reg_array_length;

	ret = get_data(&cmd_ret, block_id, reg_name);
	if (ret) {
		printf("hikptool roce_%s get data failed!\n", cmd_name);
		goto get_data_error;
	}

	ret = fill_output(cmd_ret, cmd_name, block_id, output);
	if (ret)
		goto get_data_error;

	ret = validate_block_numbers(res_head, cmd_name, block_id);
	if (ret) {
		hikp_roce_ext_reg_data_free(reg);
		goto get_data_error;
	}

	ret = copy_data_from_response(cmd_ret, output, reg_array_length, cmd_name, block_id);
	if (ret)
		hikp_roce_ext_reg_data_free(reg);

get_data_error:
	hikp_cmd_free(&cmd_ret);
	return ret;
}

static void hikp_roce_ext_print(enum roce_cmd_type cmd_type,
				struct roce_ext_res_output *output)
{
	uint32_t total_block_num = output->res_head.total_block_num;
	const char **reg_name = output->reg_name.reg_name;
	const char *cmd_name = get_cmd_name(cmd_type);
	uint8_t arr_len = output->reg_name.arr_len;
	uint32_t *offset = output->reg.offset;
	struct reg_data *reg = &output->reg;
	const char *name;
	uint32_t i;

	printf("**************%s INFO*************\n", cmd_name);
	printf("%-40s[addr_offset] : reg_data\n", "reg_name");
	for (i = 0; i < total_block_num; i++) {
		name = i < arr_len ? reg_name[i] : "";
		printf("%-40s[0x%08X] : ", name, offset[i]);
		if (output->res_head.flags & ROCE_HIKP_DATA_U64_FLAG)
			printf("0x%016lX\n", reg->data_u64[i]);
		else
			printf("0x%08X\n", reg->data_u32[i]);
	}
	printf("************************************\n");
}

void hikp_roce_ext_execute(struct major_cmd_ctrl *self,
			   enum roce_cmd_type cmd_type,
			   int (*get_data)(struct hikp_cmd_ret **cmd_ret,
					   uint32_t block_id,
					   struct roce_ext_reg_name *reg_name))
{
	struct roce_ext_res_output output = { 0 };
	uint32_t queried_block_id = 0;

	do {
		self->err_no = hikp_roce_ext_get_res(cmd_type, queried_block_id,
						     &output, get_data);
		if (self->err_no)
			return;

		queried_block_id += output.res_head.cur_block_num;
	} while (queried_block_id < output.res_head.total_block_num);

	hikp_roce_ext_print(cmd_type, &output);

	hikp_roce_ext_reg_data_free(&output.reg);
}
